package fons.model.ontology;

import java.util.Map;
import java.util.Map.Entry;

import com.google.common.collect.Maps;

import fons.model.ontology.interfaces.Measurement;

public class CompositeMeasurement implements Measurement {
	private final Map<Measurement, Integer> measurements;
	private final String type;
	private final String unit;
	private final String qualifiedName;
	
	private CompositeMeasurement(Map<Measurement, Integer> measurements) {
		this.measurements = measurements;
		StringBuilder typeBuilder = new StringBuilder();
		StringBuilder unitBuilder = new StringBuilder();
		StringBuilder nameBuilder = new StringBuilder();
		
		boolean firstTime = true;
		for (Entry<Measurement, Integer> entry: measurements.entrySet()) {
			if (firstTime == false) {
				typeBuilder.append('*');
				unitBuilder.append('*');
				nameBuilder.append('*');
			}
			typeBuilder.append(entry.getKey().getType() + "^(" + entry.getValue() + ")");
			unitBuilder.append(entry.getKey().getUnit() + "^(" + entry.getValue() + ")");
			nameBuilder.append(entry.getKey().getQualifiedName() + "^(" + entry.getValue() + ")");
			firstTime = false;
		}
		
		this.type = typeBuilder.toString();
		this.unit = unitBuilder.toString();
		this.qualifiedName = nameBuilder.toString();
	}
	
	public static Measurement multiply (Measurement a, Measurement b) {
		return CompositeMeasurement.combine(a, b, true);
	}
	
	public static Measurement divide (Measurement a, Measurement byB) {
		return CompositeMeasurement.combine(a, byB, false);
	}
	
	private static Measurement combine (Measurement a, Measurement b, boolean multiply) {
		final Map<Measurement, Integer> measurements = Maps.newHashMap();
		
		if (a == null) return b;
		if (b == null) return a;
		
		if (a instanceof CompositeMeasurement) {
			measurements.putAll(((CompositeMeasurement) a).measurements);
		} else {
			measurements.put(a, 1);
		}
		
		if (b instanceof CompositeMeasurement) {
			
			CompositeMeasurement other = (CompositeMeasurement) b;
			for (Measurement m: other.measurements.keySet()) {
				int n = 0;
				if (measurements.containsKey(m)) {
					n = multiply ? measurements.get(m)+other.measurements.get(m) :
								   measurements.get(m)-other.measurements.get(m);
				} else {
					n = (multiply ? 1 : -1) * other.measurements.get(m);
				}
				
				if (n == 0) {
					measurements.remove(m);
				} else {
					measurements.put(m, n);
				}
			}
		} else {
			
			if (measurements.containsKey(b)) {
				int n = multiply ? measurements.get(b)+1 : measurements.get(b)-1;
				if (n == 0) {
					measurements.remove(b);
				} else {
					measurements.put(b, n);
				}
			} else {
				measurements.put(b, multiply ? 1 : -1);
			}
		}
		
		if (measurements.isEmpty()) return null;
		return new CompositeMeasurement(measurements);
	}
	
	
	
	@Override
	public String getType() {
		return this.type;
	}

	@Override
	public String getUnit() {
		return this.unit;
	}

	@Override
	public String getQualifiedName() {
		return this.qualifiedName;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((measurements == null) ? 0 : measurements.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		CompositeMeasurement other = (CompositeMeasurement) obj;
		if (measurements == null) {
			if (other.measurements != null)
				return false;
		} else if (!measurements.equals(other.measurements))
			return false;
		return true;
	}	
}
